Skip to content

Conversation

@StrawberrySmoothieDev
Copy link

First off, this is HEAVILY based on koksnull's work in PR #144, with tweaks made to allow it to work w/modern libraries/main repo version. Note that I rarely if ever use C#, so expect to see some horrific code in here somewhere. I am fully open to any suggestions/changes.

This adds proxy functionality to the hub (via happyeyeballshttp) allowing for proxied server discovery, build downloads, etc.
I've tested this on OSX with a socks5 ssh tunnel proxy, and if anyone has other proxies they can test it with please do.

Known issues:

  • Connecting to servers can cause an ssl error (as in the game instance itself does not use the proxy. Not sure if that can be fixed w/o changing every client build but idk, I work around it via a temp. tsocks proxy. Your mileage may vary, could be an osx thing.
  • Should either be editable from the login menu, or reset upon failed connection. I'm not sure how to do either.
  • Makes it impossible to join a LAN server w/o port forwarding it, as all traffic is directed through the proxy.

Bit of a mess, but this is my first time. Hopefully y'all can see what I can't so this can get merged. <3

Very rough code. I never really use C# to be honest, and my networking experience is lacking, but it does work, and fairly well.
Copy link
Member

@VasilisThePikachu VasilisThePikachu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTICE: I am not a maintainer for the Launcher repo, these are suggestions

  1. The proxy will preferably be passed down to the client via environment variables, since the game client also has to go through the auth server at least once, which (usually) is subject to being blocked. (SOCKS5 can also pass UDP traffic, which to some can be useful)

This however may be out of scope for this pr, I would let this be decided by @PJB3005

I would at least have it passed down now for someone else to code later on to the engine.

  1. <CheckBox DockPanel.Dock="Left" VerticalAlignment="Center" Margin="4" IsChecked="{Binding LogLauncher}" Content="{loc:Loc login-log-launcher}" />
    I would say do it here next to the log launcher button

  2. With the way this is currently coded, not sure how i would solve this... although you would at least need to check if we are connecting to an internal ip address (Upstream has a system you can probably borrow https://github.com/space-wizards/space-station-14/blob/45e5fbfcdf824de5b0416895b475a7c6ad386f0f/Content.Server/Connection/IPIntel/IPIntel.cs#L287-L377) and ignore running that through the proxy.

Comment on lines 69 to 73
<CheckBox VerticalAlignment="Center" Margin="4" IsChecked="{Binding ProxyEnable}">Enable proxy</CheckBox>
<TextBox Grid.Column="1" Watermark="http://example.com:80" Text="{Binding ProxyURL}" />
</Grid>
<TextBlock VerticalAlignment="Center" TextWrapping="Wrap"
Text="Proxy information for connect to hub and auth center. (requires launcher restart)"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to use localization strings instead so that people can translate everything.

Look at the other options above, and add it into the English text.ftl

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. Thanks for your help! <3

Comment on lines 70 to 71
<TextBox Grid.Column="1" Watermark="http://example.com:80" Text="{Binding ProxyURL}" />
</Grid>
Copy link
Member

@VasilisThePikachu VasilisThePikachu Mar 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make the watermark something like

http://optional-username:optional-password@example.com:80

Perhaps also localized IF possible.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, and will do, but it's worth keeping in mind socks5 proxies require a different syntax
socks5://127.0.0.1:1337

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know, I dont want it to be too big though. You may point it out somewhere else perhaps

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. Does avalonia support tooltips (ie text that pops up on hover)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it does, you will need to search that up

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got the tooltip working

@StrawberrySmoothieDev
Copy link
Author

Note: Just tested the local URL changes. Connecting to the server and getting to the handshake stage works fine, but errors out with [DEBG] net: "IP_ADDRESS:PORT": Status changed to Disconnected, reason: "Unknown server error occured during handshake." My guess is the proxy info isn't being passed to the application for the authentication stage, but that doesn't make sense as joining external servers works just fine. Weird stuff.

@VasilisThePikachu
Copy link
Member

VasilisThePikachu commented Mar 12, 2025

Note: Just tested the local URL changes. Connecting to the server and getting to the handshake stage works fine, but errors out with [DEBG] net: "IP_ADDRESS:PORT": Status changed to Disconnected, reason: "Unknown server error occured during handshake." My guess is the proxy info isn't being passed to the application for the authentication stage, but that doesn't make sense as joining external servers works just fine. Weird stuff.

That sounds unrelated. Look at the stack trace on the client for a fuller picture.

@StrawberrySmoothieDev
Copy link
Author

Note: Just tested the local URL changes. Connecting to the server and getting to the handshake stage works fine, but errors out with [DEBG] net: "IP_ADDRESS:PORT": Status changed to Disconnected, reason: "Unknown server error occured during handshake." My guess is the proxy info isn't being passed to the application for the authentication stage, but that doesn't make sense as joining external servers works just fine. Weird stuff.

That sounds unrelated.

If you don't mind me asking, how so? It means local servers may still not work under proxy.

@VasilisThePikachu
Copy link
Member

Without the full error from the client. I can only make assumptions. The github comments are not the best place to have chatter about troubleshooting this, so it's probably best to talk on discord.

@StrawberrySmoothieDev
Copy link
Author

Without the full error from the client. I can only make assumptions. The github comments are not the best place to have chatter about troubleshooting this, so it's probably best to talk on discord.

Got it. This is generally not a huge issue either way, as it can be worked around w/port forwarding, but I'll do some testing whenever I can.

@VasilisThePikachu
Copy link
Member

VasilisThePikachu commented Mar 12, 2025

IMO this is an issue that should be solved. Not all players will port forward to play on a server running on their OWN computer. Let alone have the expertise.

@StrawberrySmoothieDev
Copy link
Author

StrawberrySmoothieDev commented Mar 12, 2025

I'm no network engineer so this is mostly beyond me but if this tells you something lmk

My best guess? The robust engine network requests are maybe not getting proxied? Or perhaps the server needs to be proxied? I have no idea to be honest. Sorry if this isn't helpful, I'll be able to test this better later.
Notes:
Proxy via socks5 SSH tunnel
Server is default wizden latest OSX build
Connects fine to goobstation/wizden public severs
This error (or a similar one) has come up in other situations where a connection was attempted and failed due to lacking a proxy.

LOCAL CONNECTION ERROR MESSAGE
[INFO] statushost.http: GET /info from 127.0.0.1:51999 [DEBG] net: "127.0.0.1:55006": Status changed to RespondedAwaitingApproval, reason: "Awaiting approval" [DEBG] net: "127.0.0.1:55006": Status changed to RespondedConnect, reason: "Remotely requested connect" [DEBG] net: "127.0.0.1:55006": Status changed to Connected, reason: "Connected to 395E3BECC5A46E63" [ERRO] net: Exception during handshake with peer 395E3BECC5A46E63: System.Text.Json.JsonException: '<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0. ---> System.Text.Json.JsonReaderException: '<' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 0. at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan 1 bytes) at System.Text.Json.Utf8JsonReader.ConsumeValue(Byte marker) at System.Text.Json.Utf8JsonReader.ReadFirstToken(Byte first) at System.Text.Json.Utf8JsonReader.ReadSingleSegment() at System.Text.Json.Utf8JsonReader.Read() at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, T& value, JsonSerializerOptions options, ReadStack& state) --- End of inner exception stack trace --- at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, JsonReaderException ex) at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, T& value, JsonSerializerOptions options, ReadStack& state) at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.ContinueDeserialize(ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack, T& value) at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsync(Stream utf8Json, CancellationToken cancellationToken) at System.Net.Http.Json.HttpClientJsonExtensions.<FromJsonAsyncCore>g__Core|12_0[TValue,TJsonOptions](HttpClient client, Task`1 responseTask, Boolean usingResponseHeadersRead, CancellationTokenSource linkedCTS, Func`4 deserializeMethod, TJsonOptions jsonOptions, CancellationToken cancellationToken) at Robust.Shared.Network.NetManager.HandleHandshake(NetPeerData peer, NetConnection connection) in /home/runner/work/space-station-14/space-station-14/RobustToolbox/Robust.Shared/Network/NetManager.ServerAuth.cs:line 144 [DEBG] net: "127.0.0.1:55006": Status changed to Disconnecting, reason: "Unknown server error occured during handshake." [DEBG] net: "127.0.0.1:55006": Status changed to Disconnected, reason: "Unknown server error occured during handshake."

};
if (!String.IsNullOrEmpty(proxyUrl)){ //Note: I'm VERY new to writing c#. This is heavily copied from koksnull's work in https://github.com/space-wizards/SS14.Launcher/pull/144 . I've just made some adjustments to make it work for the modern versions of .net
//I'm unsure of how to go about this, but having some way to sanity check the proxy before activating it (i.e. a test ping) would be great.
Uri proxyURI = new Uri(proxyUrl.Trim('"'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the value isn't a valid URI, this will cause the launcher to crash on startup. You're gonna need a way to handle that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever the reason for this proxyUrl.Trim('"') thing is, it needs to be figured out and removed for sure.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the value isn't a valid URI, this will cause the launcher to crash on startup. You're gonna need a way to handle that.

Fixed this in 8b1067f

WebProxy clientProxy = new WebProxy(proxyUrl.Trim('"')); //No clue why .trim('"') is required. C# jank? Doesn't work otherwise.
clientProxy.BypassProxyOnLocal = true;
if (!string.IsNullOrWhiteSpace(proxyURI.UserInfo)){
string[] credentials = proxyURI.UserInfo.Split(new[] { ':' });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Splitting like this means that if the password has a : character in it, it won't get accepted correctly. Probably better to use IndexOf and only split on the first occurrence.

Comment on lines +68 to +74
<Grid ColumnDefinitions="Auto,*">
<CheckBox VerticalAlignment="Center" Margin="4" IsChecked="{Binding ProxyEnable}" Content="{loc:Loc tab-options-desc-proxy-enable}" />
<TextBox Grid.Column="1" Watermark="http://optional-username:optional-password@example.com:80" Text="{Binding ProxyURL}" ToolTip.Tip="{loc:Loc tab-options-desc-proxy-tooltip}"/>
</Grid>
<TextBlock VerticalAlignment="Center" TextWrapping="Wrap"
Text="{loc:Loc tab-options-desc-proxy-address}"
Margin="8" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have some form of error reporting for in case the URL is misformatted.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have some form of error reporting for in case the URL is misformatted.

How would I go about this? I'd need some way to run Uri.TryCreate and I'm not exactly sure how to do that in avalonia. Would I run it in the UI script and show/hide a line of text indicating it?

@PJB3005
Copy link
Member

PJB3005 commented Mar 20, 2025

Yes, having some sort of way to pass the proxy config to the client engine is gonna be a must.

@StrawberrySmoothieDev
Copy link
Author

Yes, having some sort of way to pass the proxy config to the client engine is gonna be a must.

Does the client engine even have proxy support? Might need to make a separate PR to robust toolbox or something. Thanks for the reviews, I'll do my best to fix it up :3

@PJB3005
Copy link
Member

PJB3005 commented Mar 20, 2025

Does the client engine even have proxy support? Might need to make a separate PR to robust toolbox or something.

Yeah, you're gonna need to add that.

@StrawberrySmoothieDev
Copy link
Author

Does the client engine even have proxy support? Might need to make a separate PR to robust toolbox or something.

Yeah, you're gonna need to add that.

As I've mentioned before, I'm pretty new to C#, but a cursory look at the happy eyeballs implementation in robust toolbox tells me I should be able to do the same thing, but instead take the proxy address from the environment variables. I have no idea how to do this, but given enough time I should be able to figure it out. Any help/pointers would be greatly appreciated :3

@PJB3005
Copy link
Member

PJB3005 commented Mar 22, 2025

As I've mentioned before, I'm pretty new to C#, but a cursory look at the happy eyeballs implementation in robust toolbox tells me I should be able to do the same thing, but instead take the proxy address from the environment variables. I have no idea how to do this, but given enough time I should be able to figure it out. Any help/pointers would be greatly appreciated :3

Edit Connector.LaunchClient() to pipe the proxy config into an env var, then edit RT to read the env var. See also how to test RT changes against the launcher

@koksnull
Copy link

Ohhhh, I forgot I was supposed to finish that PR...

@koksnull
Copy link

May I continue my work on this task? Since this task has been hanging since mar

@VasilisThePikachu
Copy link
Member

You are more than free to if you want, no need to ask us, you have had requested changes that you need to solve before we can consider it. Most important being piping the proxy info robust toolbox

@github-actions
Copy link

This pull request has conflicts, please resolve those before we can evaluate the pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants